using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace PhpDocBlockGenerator.Model.Php
{
    /// <summary>
    /// base class to represent the data of a php doc block
    /// </summary>
    public abstract class PhpDocBlock
    {
        /// <summary>
        /// the @author property in the doc block
        /// </summary>
        private List<DocBlockProperty> _author = new List<DocBlockProperty>();
        public List<DocBlockProperty> Author
        {
            get { return _author; }
            set { _author = value; }
        }

        /// <summary>
        /// the line where the doc block begins
        /// </summary>
        private int _docBlockBegin = -1;
        public int DocBlockBegin
        {
            get { return _docBlockBegin; }
            set { _docBlockBegin = value; }
        }        

        /// <summary>
        /// the line where the doc block ends
        /// </summary>
        private int _docBlockEnd = -1;
        public int DocBlockEnd
        {
            get { return _docBlockEnd; }
            set { _docBlockEnd = value; }
        }

        /// <summary>
        /// All E-Mails which have been replaced by Placeholders
        /// </summary>
        protected List<String> EMailAdresses = new List<String>();

        /// <summary>
        /// A list for other properties like access, deprecated, see, etc.
        /// </summary>
        private List<DocBlockProperty> _otherProperties = new List<DocBlockProperty>();
        public List<DocBlockProperty> OtherProperties
        {
            get { return _otherProperties; }
            set { _otherProperties = value; }
        }

        /// <summary>
        /// the short description in the doc block
        /// </summary>
        public DocBlockProperty ShortDescription { get; set; }

        /// <summary>
        /// the @version property in the doc block
        /// </summary>
        public DocBlockProperty Version { get; set; }

        /// <summary>
        /// flag which indicates if the doc block is generated by the tool or
        /// read from the file (it's called user doc block)
        /// </summary>
        public bool IsUserBlock { get; set; }

        /// <summary>
        /// methods which sets the several setter for the doc block properties
        /// </summary>
        protected Dictionary<DocBlockPropertyTypes, Action<DocBlockProperty>> DocBlockPropertySetter 
            = new Dictionary<DocBlockPropertyTypes, Action<DocBlockProperty>>();

        /// <summary>
        /// the options container
        /// </summary>
        public Options Options { get; private set; }

        /// <summary>
        /// ctor
        /// </summary>
        protected PhpDocBlock(Options options)
        {
            IsUserBlock = false;
            Version = null;
            Options = options;
            ShortDescription = new DocBlockProperty(Options)
                {
                    Description = String.Empty,
                    ObjectType = DocBlockPropertyTypes.Description
                };
        }

        /// <summary>
        /// sets the methods which sets the right properties
        /// </summary>
        protected virtual void _ConfigurePropertySetter()
        {
            DocBlockPropertySetter[DocBlockPropertyTypes.Author] = property => Author.Add(property);
            DocBlockPropertySetter[DocBlockPropertyTypes.Version] = property => { Version = property; };
            DocBlockPropertySetter[DocBlockPropertyTypes.Misc] = property => OtherProperties.Add(property);
        }

        /// <summary>
        /// replaces the generated placeholders for e-mails with the correct data
        /// </summary>
        /// <param name="tempObj">object where to replace placeholders</param>
        protected void _FillPlaceholdersWithEmailAddresses(DocBlockProperty tempObj)
        {
            if (!String.IsNullOrWhiteSpace(tempObj.Description))
            {
                // fill placeholders with right e-mails
                MatchCollection regEmailPlaceholderResult =
                    PhpRegex.RegSearchEmailPlaceholder.Matches(tempObj.Description);

                if (regEmailPlaceholderResult.Count > 0)
                {
                    var regReplaceEmailPlaceholder = new Regex(
                        @"\<\[E-Mail\]" + regEmailPlaceholderResult[0].Value + @"\>",
                        RegexOptions.Compiled
                        );
                    tempObj.Description = regReplaceEmailPlaceholder.Replace
                        (tempObj.Description, EMailAdresses[int.Parse(regEmailPlaceholderResult[0].Value)]);
                }
            }
        }

        /// <summary>
        /// Searches E-Mail Adresses and Replace it with a Placeholder
        /// </summary>
        /// <param name="allLines"></param>
        /// <returns></returns>
        protected String _SearchAndReplaceMailAddresses(String allLines)
        {
            // Search for E-Mail-Adresses
            MatchCollection regEmailResult = PhpRegex.RegSearchEmailAdress.Matches(allLines);
            if (regEmailResult.Count > 0)
            {
                for (int i = 0; i < regEmailResult.Count; ++i)
                {
                    var actualMail = new Regex(regEmailResult[i].Value);
                    allLines = actualMail.Replace(allLines, "<[E-Mail]" + i + ">");
                    EMailAdresses.Add(regEmailResult[i].Value);
                }
            }
            return allLines;
        }

        /// <summary>
        /// determines the indenting for the property descriptions
        /// </summary>
        /// <returns></returns>
        protected int DetermineIndenting()
        {
            int indent = 0;
            if (_author != null && _author.Count > 0) {
                indent = 6;
            }
            if (Version != null)
                indent = 7;
            return _otherProperties.Select(prop => prop.TypeName.Length).Concat(new[] {indent}).Max();
        }

        /// <summary>
        /// Parses a docblock out of a given List of Lines
        /// </summary>
        /// <param name="fileArr">list of docblocklines</param>
        public virtual void Parse(List<String> fileArr)
        {
            // at first set the handlings for the several types
            _ConfigurePropertySetter();

            //concat all lines to one string
            String allLines = fileArr.Aggregate(String.Empty, (current, line) => current + (line + " \n"));

            //filter docblock (eras0riz0r /** * */)
            //delete all specific chars of a php doc block and remove all unnecessary line breaks
            allLines = PhpRegex.RegDeleteSpecialChars.Replace(allLines, "\n");
            allLines = PhpRegex.RegDeleteNeedlessLinebreaks.Replace(allLines, "");

            // Search Mail Adresses and replace them with Placeholders
            // (to avoid false property-regex with @'s)
            allLines = _SearchAndReplaceMailAddresses(allLines);

            //search the remaining content for a description
            MatchCollection regDescriptionResult = PhpRegex.RegSearchDescription.Matches(allLines);

            //if a description was found
            if (regDescriptionResult.Count > 0)
            {
                //remove all unnecessary line breaks, extract it and save it
                String temp = PhpRegex.RegDeleteNeedlessLinebreaks.Replace(regDescriptionResult[0].Value, "").Trim();
                if (!temp.Equals(String.Empty))
                {
                    var tempProp = new DocBlockProperty(Options)
                    {
                        Description = temp,
                        ObjectType = DocBlockPropertyTypes.Description
                    };
                    tempProp.CreateDescriptionList();
                    ShortDescription = tempProp;
                }
            }

            //search all doc block properties in the content (all @'s)
            MatchCollection regAtResult = PhpRegex.RegSearchAtSign.Matches(allLines);

            //loop over all matches
            foreach (Match match in regAtResult)
            {
                //parse the property to find out the type
                var tempObj = new DocBlockProperty(Options);
                tempObj.Parse(match.Value);

                // fill placeholders with the correct mails
                _FillPlaceholdersWithEmailAddresses(tempObj);

                if (DocBlockPropertySetter.ContainsKey(tempObj.ObjectType))
                {
                    DocBlockPropertySetter[tempObj.ObjectType](tempObj);
                }
                else
                {
                    DocBlockPropertySetter[DocBlockPropertyTypes.Misc](tempObj);
                }
            }

            //we read an existing doc block -> set user doc block flag
            IsUserBlock = true;
        }

        /// <summary>
        /// creates the doc block as a list of lines
        /// </summary>
        /// <param name="props">properties which will be added to the doc block</param>
        /// <param name="indent">the indeting before the doc block begins</param>
        /// <param name="indents">all the indentings for the property structure</param>
        /// <returns>a list of doc block lines</returns>
        protected List<String> _CreateDocBlockAsList(List<DocBlockProperty> props, String indent, int[] indents)
        {
            // create the list
            var list = new List<String>();

            //add begin of block
            list.Add(indent + "/**");

            // check if a property will be written
            bool hasProperty = props.Exists(x => !DocBlockProperty.IsNullOrEmpty(x));

            //add description
            if (ShortDescription.DescriptionList == null)
            {
                if (ShortDescription.Description != null)
                {
                    list.Add(indent + " * " + ShortDescription.Description.Trim());
                    if (hasProperty)
                        list.Add(indent + " *");
                }
            }
            else
            {
                foreach (String line in ShortDescription.DescriptionList)
                {
                    list.Add(indent + " * " + line.Trim());
                }
                if (hasProperty)
                    list.Add(indent + " *");
            }

            // add properties
            foreach (DocBlockProperty prop in props)
            {
                if (!DocBlockProperty.IsNullOrEmpty(prop))
                    list.AddRange(prop.ToStringList(indent, indents));
            }

            //add end of block
            list.Add(indent + " */");

            return list;
        }
    }

    /// <summary>
    /// doc block of a property
    /// </summary>
    public class PhpPropertyDocBlock : PhpDocBlock
    {
        /// <summary>
        /// the var property
        /// </summary>
        public DocBlockPropertyVariable Var { get; private set; }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="options"></param>
        public PhpPropertyDocBlock(Options options)
            : base(options)
        {
            Var = new DocBlockPropertyVariable(options) { ObjectType = DocBlockPropertyTypes.Var };
        }

        /// <summary>
        /// sets the correct type handlings
        /// </summary>
        protected override void _ConfigurePropertySetter()
        {
            base._ConfigurePropertySetter();
            DocBlockPropertySetter[DocBlockPropertyTypes.Var] 
                = property => { Var = new DocBlockPropertyVariable(property, Options); };
        }

        /// <summary>
        /// creates a list of doc block lines from the object
        /// </summary>
        /// <param name="indent">indeting before the doc block starts</param>
        /// <returns>a list of lines</returns>
        public List<string> ToList(String indent)
        {
            var propList = new List<DocBlockProperty>();
            propList.AddRange(Author);
            propList.AddRange(OtherProperties);
            propList.Add(Var);
            propList.Add(Version);
            int indenting = DetermineIndenting();

            return _CreateDocBlockAsList(propList, indent, new[] { indenting, 0});
        }

        /// <summary>
        /// Compares two PropertyDocBlocks and creates a merged one
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public PhpPropertyDocBlock Compare(PhpPropertyDocBlock other)
        {
            // check which doc block is userbuild and which one is generated
            PhpPropertyDocBlock userBlock, generatedBlock;
            if (IsUserBlock && !other.IsUserBlock)
            {
                userBlock = this;
                generatedBlock = other;
            }
            else if (!IsUserBlock && other.IsUserBlock)
            {
                userBlock = other;
                generatedBlock = this;
            }
            else
                throw new Exception("Not exactly one userblock and one generated block given");

            // create merged one
            var mergedBlock = new PhpPropertyDocBlock(Options)
                {
                    // check for description
                    ShortDescription = String.IsNullOrEmpty(userBlock.ShortDescription.Description)
                                           ? generatedBlock.ShortDescription
                                           : userBlock.ShortDescription,
                    // take unknown properties
                    OtherProperties = userBlock.OtherProperties,
                    // take the user variable if existant
                    Var = userBlock.Var.VariableDataType.Type == PhpDataTypes.None 
                            ? generatedBlock.Var 
                            : userBlock.Var,
                };

            return mergedBlock;
        }
    }

    /// <summary>
    /// doc block of a method
    /// </summary>
    public class PhpMethodDocBlock : PhpDocBlock
    {
        /// <summary>
        /// the list of @param properties in the docblock
        /// </summary>
        private List<DocBlockPropertyParam> _params = new List<DocBlockPropertyParam>();

        public PhpMethodDocBlock(Options options) : base(options)
        {
        }

        public List<DocBlockPropertyParam> Params
        {
            get { return _params; }
            set { _params = value; }
        }

        /// <summary>
        /// the @return property in the docblock
        /// </summary>
        private List<DocBlockPropertyReturn> _return = new List<DocBlockPropertyReturn>();
        public List<DocBlockPropertyReturn> Return
        {
            get { return _return; }
            set { _return = value; }
        }

        /// <summary>
        /// the list of @throws properties in the docblock
        /// </summary>
        private List<DocBlockPropertyException> _throws = new List<DocBlockPropertyException>();
        public List<DocBlockPropertyException> Throws
        {
            get { return _throws; }
            set { _throws = value; }
        }

        /// <summary>
        /// compares the doc block to another doc block and merges them
        /// (needs a generated doc block and an existing doc block read from file; if both
        /// block objects don't fit these requirements an exception will be thrown)
        /// </summary>
        /// <param name="other">doc block to compare to</param>
        /// <returns>merged doc block</returns>
        public PhpMethodDocBlock Compare(PhpMethodDocBlock other)
        {
            PhpMethodDocBlock userBlock, generatedBlock;

            //decide which doc block is an existing and which one is the generated
            if (IsUserBlock && !other.IsUserBlock)
            {
                userBlock = this;
                generatedBlock = other;
            }
            else if (!IsUserBlock && other.IsUserBlock)
            {
                userBlock = other;
                generatedBlock = this;
            }
                //if there is no existing or no generated then throw exception
            else
                throw new Exception("");


            var mergedBlock = new PhpMethodDocBlock(Options)
                {
                    //take authors from "old" user doc block and add generator itself as author
                    Author = userBlock.Author,
                    //take version from "old" user doc block
                    Version = userBlock.Version,
                    //take other props from "old" user doc block
                    OtherProperties = userBlock.OtherProperties
                };

            //if user description exists then take it otherwise use function name as description
            //(which is saved in the description of the generated block)
            if (String.IsNullOrEmpty(userBlock.ShortDescription.Description))
                mergedBlock.ShortDescription = generatedBlock.ShortDescription;
            else
                mergedBlock.ShortDescription = userBlock.ShortDescription;

            //merge exceptions from both blocks and extract unique ones
            //(we could test only our exceptions, but if there is a called method which
            //can throw an exception which the user writes into the doc block then we
            //would delete that information, so we concat them all)
            var exceptions = (from ue in userBlock.Throws.Concat(generatedBlock.Throws)
                              select ue).Distinct(new DocBlockPropertyExceptionComparer());
            mergedBlock.Throws = exceptions.ToList();

            //get unique params of both blocks
            //(prefer the ones with description)
            mergedBlock.Params = _MergeParams(userBlock, generatedBlock);

            //get merged return type
            if (userBlock.Return.Count > 0)
                mergedBlock.Return = userBlock.Return;
            else
                mergedBlock.Return = generatedBlock.Return;

            //if the generator adds some things, author is the docblock-gen too
            //if (MergedBlock != UserBlock)
            //{
            //    DocBlockProperty Author = new DocBlockProperty();
            //    Author.ObjectType = DocBlockPropertyTypes.Author;
            //    Author.Description = DocBlockProperty._AuthorStandardText;
            //}

            return mergedBlock;
        }

        /// <summary>
        /// sets the handlings for the different types
        /// </summary>
        protected override void _ConfigurePropertySetter()
        {
            base._ConfigurePropertySetter();
            DocBlockPropertySetter[DocBlockPropertyTypes.Param] = property => Params.Add(new DocBlockPropertyParam(property, Options));
            DocBlockPropertySetter[DocBlockPropertyTypes.Return] = property => Return.Add(new DocBlockPropertyReturn(property, Options));
            DocBlockPropertySetter[DocBlockPropertyTypes.Throws] = property => Throws.Add(new DocBlockPropertyException(property, Options));
        }

        /// <summary>
        /// converts the file doc block to a list of strings (linewise)
        /// </summary>
        /// <param name="indent">indent to add before every line string</param>
        /// <returns>list of strings which represents the single lines in a file of the block</returns>
        public List<String> ToList(String indent)
        {
            // get the length of the typename
            int paramMaxTypeIndent = Params.Count > 0 ? Params.Max(x => x.GetTypeIndent()) : 0;
            // get the length of the longest "@throws" doc
            int throwsMaxDescriptionIndent = Throws.Count > 0 ? Throws.Max(x => x.GetDescriptionIndent()) : 0;
            // get the length of the longest return doc
            int returnMaxDescriptionIndent = Return.Count > 0 ? Return.Max(x => x.GetDescriptionIndent()) : 0;

            // throw it into a list to get the highest value per linq
            var maxParamIndentList = new List<int>
                {
                    paramMaxTypeIndent,
                    throwsMaxDescriptionIndent,
                    returnMaxDescriptionIndent
                };
            // get the highest value
            int maxParamIndent = maxParamIndentList.Max(x => x);

            // get the longest indent for params
            int paramMaxDescriptionIndent = Params.Count > 0
                                                ? Params.Max(x => x.GetDescriptionIndent(maxParamIndent))
                                                : 0;
            // again throw all into a list
            var maxIndents = new List<int>
                {
                    paramMaxDescriptionIndent,
                    throwsMaxDescriptionIndent,
                    returnMaxDescriptionIndent
                };
            // get the highest value for descriptions
            int descriptionIndent = maxIndents.Max(x => x);

            // now get the type indenting
            var typeIndenting = DetermineIndenting();

            // put this into a list
            var indents = new[]
                {
                    descriptionIndent + typeIndenting, 
                    typeIndenting,
                    maxParamIndent + typeIndenting
                };

            // define the properties
            var propList = new List<DocBlockProperty>();
            propList.AddRange(OtherProperties);
            propList.AddRange(Author);
            propList.AddRange(Params);
            propList.AddRange(Throws);
            propList.AddRange(Return);
            propList.Add(Version);
            return _CreateDocBlockAsList(propList, indent, indents);
        }

        /// <summary>
        /// merges all params of the user doc block and the generated doc block and extracts
        /// the unique ones
        /// (prefers params with description in case of double params)
        /// </summary>
        /// <param name="userBlock">user doc block</param>
        /// <param name="generatedBlock">generated doc block</param>
        /// <returns>merged list of unique params</returns>
        private List<DocBlockPropertyParam> _MergeParams(PhpMethodDocBlock userBlock, PhpMethodDocBlock generatedBlock)
        {
            List<DocBlockPropertyParam> newParamList = new List<DocBlockPropertyParam>();


            //loop over all generated params (because "our" param count needs to be right)
            for (int i = 0; i < generatedBlock.Params.Count; ++i)
            {
                DocBlockPropertyParam param = generatedBlock.Params[i];

                //find current param in user block
                var matchingParam = userBlock.Params.Find(p => p.VariableName.Equals(param.VariableName));

                //if param was found in user block
                if (null != matchingParam)
                {
                    //in case of empty (user param) description and a specified (user param) type
                    //add user param to list (because we lost no information, we also don't have it)
                    //otherwise we have more information about the type at least so add our param to list
                    if (!matchingParam.VariableDataType.ToString().Equals(param.VariableDataType.ToString()))
                    {
                        if (!String.IsNullOrEmpty(matchingParam.Description))
                            param.Description = matchingParam.Description;
                        newParamList.Add(param);
                    }
                    else if (matchingParam.VariableDataType.Type != PhpDataTypes.None)
                        newParamList.Add(matchingParam);
                    else
                        newParamList.Add(param);
                }
                    //otherwise there was no match in the user param list
                else
                {
                    //params in the doc block must be in right order so check so corresponding
                    //user param by type equalitiy
                    if (userBlock.Params.Count > i &&
                        param.VariableDataType.Type == userBlock.Params[i].VariableDataType.Type)
                    {
                        //WARNING:
                        //at that point our algorithm could be wrong, the types have the chance to
                        //be the same but the params might not be for the same variable

                        var tempParam = new DocBlockPropertyParam(Options) { VariableName = param.VariableName };
                        tempParam.VariableDataType.SetTypeFromString(param.VariableDataType.ToString());

                        //if user description exists then save it
                        if (!String.IsNullOrEmpty(userBlock.Params[i].Description))
                            tempParam.Description = userBlock.Params[i].Description;

                        //add newly created param to list
                        newParamList.Add(tempParam);
                    }
                        //otherwise add generated param to list
                    else
                        newParamList.Add(param);
                }
            }


            return newParamList;
        }

        /// <summary>
        /// determines the indenting to write the Descriptions in one column
        /// </summary>
        /// <returns></returns>
        protected new int DetermineIndenting()
        {
            int indent = base.DetermineIndenting();
            if (Params.Count != 0 && indent < 5)
                indent = 5;
            if (Return != null && Return.Count > 0 && indent < 6 && Return.Exists(x => !x.IsEmpty()))
                indent = 6;
            if (Throws != null && Throws.Count > 0 && indent < 6)
                indent = 6;

            return indent;
        }
    }

    /// <summary>
    /// doc block of a class
    /// </summary>
    public class PhpClassDocBlock : PhpDocBlock
    {
        /// <summary>
        /// the @copyright property in the file-docblock
        /// default: "Copyright (c) 2011 Unister GmbH"
        /// </summary>
        public DocBlockProperty Copyright { get; set; }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="options"></param>
        public PhpClassDocBlock(Options options)
            : base(options)
        {
            // TODO Add Custom Copyright field
            Copyright = new DocBlockProperty(options) { ObjectType = DocBlockPropertyTypes.Copyright };
        }

        /// <summary>
        /// the @package property in the file-docblock
        /// </summary>
        public DocBlockProperty Package { get; set; }

        /// <summary>
        /// compares the doc block object with another object
        /// (must be a read [from file] AND a generated doc block)
        /// and returns a merged doc block object
        /// </summary>
        /// <param name="other">object to compare to</param>
        /// <returns>null or merged doc block</returns>
        public PhpClassDocBlock Compare(PhpClassDocBlock other)
        {
            PhpClassDocBlock userBlock = null;
            PhpClassDocBlock genBlock = null;


            //find out which one is the read doc block and which one the
            //generated
            if (IsUserBlock && !other.IsUserBlock)
            {
                userBlock = this;
                genBlock = other;
            }
            else if (!IsUserBlock && other.IsUserBlock)
            {
                userBlock = other;
                genBlock = this;
            }


            //if there was a read doc block found (and implicitly also a generated)
            if (null != userBlock)
            {
                var mergedBlock = new PhpFileDocBlock(Options);


                //merge authors
                var distinctAuthors = userBlock.Author.Concat(genBlock.Author)
                                                      .Select(a => a)
                                                      .Distinct(new DocBlockPropertyComparer());
                mergedBlock.Author = distinctAuthors.ToList();

                //take read version when exists, otherwise standard value of ctor
                if (null != userBlock.Version)
                    mergedBlock.Version = userBlock.Version;

                //merge package name (prefer read)
                mergedBlock.Package = genBlock.Package;

                //merge description (prefer read)
                if (!String.IsNullOrWhiteSpace(userBlock.ShortDescription.Description))
                    mergedBlock.ShortDescription = userBlock.ShortDescription;
                else
                    mergedBlock.ShortDescription = genBlock.ShortDescription;

                //merge other properties (generated has no such properties so take
                //only read ones)
                if (0 != userBlock.OtherProperties.Count)
                    mergedBlock.OtherProperties = userBlock.OtherProperties;

                // Merge Copyright
                if (null != userBlock.Copyright)
                    mergedBlock.Copyright = userBlock.Copyright;
                else
                    mergedBlock.Copyright = genBlock.Copyright;


                return mergedBlock;
            }
            return null;
        }

        /// <summary>
        /// sets the special type handlings
        /// </summary>
        protected override void _ConfigurePropertySetter()
        {
            base._ConfigurePropertySetter();
            DocBlockPropertySetter[DocBlockPropertyTypes.Copyright] = property => { Copyright = property; };
            DocBlockPropertySetter[DocBlockPropertyTypes.Package] = property => { Package = property; };
        }

        /// <summary>
        /// converts the file doc block to a list of strings (linewise)
        /// </summary>
        /// <returns>list of strings which represents the single lines in a file of the block</returns>
        public List<String> ToList()
        {
            int indenting = DetermineIndenting();
            var list = new List<DocBlockProperty>();
            list.Add(Package);
            list.AddRange(OtherProperties);
            list.Add(Copyright);
            return _CreateDocBlockAsList(list, "", new[] {indenting});
        }

        protected new int DetermineIndenting()
        {
            int indent = base.DetermineIndenting();
            if (Copyright != null && indent < 9)
                indent = 9;
            if (Package != null && indent < 7)
                indent = 7;
            return indent;
        }
    }

    /// <summary>
    /// doc block of a file
    /// </summary>
    public class PhpFileDocBlock : PhpClassDocBlock
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="options"></param>
        public PhpFileDocBlock(Options options) : base(options)
        {
        }

        /// <summary>
        /// compares the doc block object with another object
        /// (must be a read [from file] AND a generated doc block)
        /// and returns a merged doc block object
        /// </summary>
        /// <param name="other">object to compare to</param>
        /// <returns>null or merged doc block</returns>
        public PhpFileDocBlock Compare(PhpFileDocBlock other)
        {
            PhpFileDocBlock userBlock = null;
            PhpFileDocBlock genBlock = null;


            //find out which one is the read doc block and which one the
            //generated
            if(IsUserBlock && !other.IsUserBlock)
            {
                userBlock = this;
                genBlock = other;
            }
            else if(!IsUserBlock && other.IsUserBlock)
            {
                userBlock = other;
                genBlock = this;
            }


            //if there was a read doc block found (and implicitly also a generated)
            if(null != userBlock)
            {
                PhpFileDocBlock mergedBlock = new PhpFileDocBlock(Options);

                // if no user authors exist take the generated ones
                if (userBlock.Author.Count == 0)
                    mergedBlock.Author = genBlock.Author;
                else
                    mergedBlock.Author = userBlock.Author;

                //take read version when exists, otherwise standard value of ctor
                if (null != userBlock.Version)
                    mergedBlock.Version = userBlock.Version;

                //merge package name (prefer read)
                mergedBlock.Package = genBlock.Package;

                //merge description (prefer read)
                if (!String.IsNullOrWhiteSpace(userBlock.ShortDescription.Description))
                    mergedBlock.ShortDescription = userBlock.ShortDescription;
                else
                    mergedBlock.ShortDescription = genBlock.ShortDescription;

                //merge other properties (generated has no such properties so take
                //only read ones)
                if (0 != userBlock.OtherProperties.Count)
                    mergedBlock.OtherProperties = userBlock.OtherProperties;
                
                return mergedBlock;
            }                
                
            return null;
        }

        /// <summary>
        /// converts the file doc block to a list of strings (linewise)
        /// </summary>
        /// <returns>list of strings which represents the single lines in a file of the block</returns>
        public new List<String> ToList()
        {
            int indenting = DetermineIndenting();
            var list = new List<DocBlockProperty>();
            list.Add(Package);
            list.AddRange(OtherProperties);
            list.Add(Copyright);
            list.AddRange(Author);
            list.Add(Version);
            return _CreateDocBlockAsList(list, "", new[] {indenting});
        }
    }
}
